<?php

namespace liketp\console;

abstract class BaseCommand
{
    protected $request = null;
    protected $params = [];
    protected $options = [];
    protected $defaultOptions = [
        // 命令选项的数据结构示例
        /*
        '--help' => [
            'key' => 'help',    //选项名称
            'type' => 'bool',   //值类型
            'default' => false, //默认值
            'count' => 0,       //值的数量
            'validate' => '',   //验证器
            'description' => 'display the help information.'    //描述
        ],
        '-h' => [
            'alias' => '--help' //别名
        ],
        */
        '--test' => [
            'type' => 'bool',
            'default' => false,
            'description' => 'Only show options、params and Not exec. For test command'
        ]
    ];

    protected function help() {}

    abstract public function exec();

    public function __construct(CommandRequest $request)
    {
        $this->request = $request;
        $this->pickArgv($request->argv());
    }

    public function before_exec()
    {
        if ($this->option('test', false)) {
            halt($this->option(), $this->param());
        }
    }

    public function after_exec() {}

    public function option($key = null, $default = null)
    {
        if (is_null($key)) {
            return $this->options;
        } else {
            if (isset($this->options[$key])) {
                return $this->options[$key];
            } else if (isset($this->defaultOptions['--' . $key])) {
                return $this->defaultOptions['--' . $key]['default'] ?? $default;
            } else {
                return $default;
            }
        }
    }

    public function param(?int $index = null, $default = null)
    {
        if (is_null($index)) {
            return $this->params;
        } else {
            return $this->params[$index] ?? $default;
        }
    }

    //------------------------------------------------ protected
    protected function pickArgv($argv)
    {
        $this->collectOptions($argv);
        $filter = array_filter($argv, function ($val) {
            return $val !== null;
        });
        $this->params = array_values($filter);

        if ($this->paramHas('--test')) {
            $this->options['test'] = true;
            $this->paramDel('--test');
        }
    }

    protected function paramHas($val)
    {
        return in_array($val, $this->params);
    }

    protected function paramDel($val, $delAll = false)
    {
        foreach ($this->params as $k => $v) {
            if ($v === $val) {
                unset($this->params[$k]);
                if ($delAll == false) return;
            }
        }
    }

    protected function collectOptions(&$argv)
    {
        foreach ($argv as $k => $v) {
            $tailVal = null;
            if (strpos($v, '=') > 0) {
                [$fullKey, $tailVal] = explode('=', $v, 2);
            } else {
                $fullKey = $v;
            }
            if ($option = $this->defaultOptions[$fullKey] ?? null) {
                if (isset($option['alias'])) {
                    $option = $this->defaultOptions[$option['alias']] ?? null;
                    array_splice($argv, $k, 1, null);
                    if (is_null($option)) continue;
                }
                $key = $option['key'] ?? ltrim($fullKey, '-');
                $len = $option['count'] ?? 1;
                $val = $this->getOptionVal($option, array_slice($argv, $k + 1, $len), $tailVal);
                $this->options[$key] = $val;
                array_splice($argv, $k, $len + 1, array_fill(0, $len + 1, null));
            }
        }
    }

    protected function getOptionVal($option, $args, $tailVal = null)
    {
        if (is_null($tailVal) == false) array_unshift($args, $tailVal);
        $res = empty($args) ? ($option['default'] ?? null) : $args;
        $type = $option['type'] ?? 'string';

        switch ($type) {
            case 'bool':
                $res = count($args) ? $args[0] : true;
                $res = in_array(strtolower($res), ['1', 'true', 'yes'], true);
                break;

            case 'string':
                $res = count($args) ? implode(',', $args) : $res;
                break;

            case 'number':
                $res = count($args) ? $args[0] : $res;
                $res = is_numeric($res) ? $res * 1 : 0;
                break;

            case 'array':
                $res = is_array($res) ? $res : [];
                break;
        }
        return $res;
    }

    //------------------------------------------------ validate methods
    protected function validateEnum($val, $enumItems)
    {
        return in_array($val, $enumItems);
    }

    protected function validateArea($val, $min, $max)
    {
        return $min <= $val && $val <= $max;
    }

    protected function validateFn($val, $fn, array $arguments = [])
    {
        return call_user_func([$this, $fn], $val, ...$arguments);
    }
}
